# CCNCore::Provision::Base.pm
# Copyright (C) 2006  Stephane Alnet
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
# 

#
# For more information visit http://carrierclass.net/
#
use strict; use warnings;

package CCNCore::Provision::Base;
use base qw(CCN::Class);

use CCNCore::Scripting;
use Digest::MD5 qw(md5_hex);

use CCNCore::ChangeSet;
sub register_on_table { CCNCore::ChangeSet::register_on_table(@_) }
sub register_on_column { CCNCore::ChangeSet::register_on_column(@_) }

# These need to be subclassed.
# By default we assume no intelligence and simply re-run() the configuration.

sub on_table($$)
{
    my $self = shift;
    my ($table,$pk) = @_;
    $self->run();
}

sub on_column($$$$$)
{
    my $self = shift;
    my ($table,$column,$pk,$new_value,$old_value) = @_;
    $self->run();
}

=pod

    my $p = new CCNCore::Provisioning::Local; # or any other CCN::Provisioning class
    my $b = new CCNCore::Provision::Base($p);

=cut

sub initialize
{
    my $self = shift;
    $self->{p} = shift;
    
    if(not defined $self->{p})
    {
        $self->die ('Internal error');
    }

    $self->register();
}

# Usage: run() is used during the post-processing stage (the stage where the configuration
#        files are written in the temporary folder).
#        It only uses the source configuration database (stored in SQLite3 format).

sub run()
{
    my $self = shift;
    $self->warn('No run() method defined.');
}

# Usage: commit() is used during the commit stage (the stage where the configuration
#        files created during the run() stage are copied in their final location).
#        Its first argument is a CCNCore::Scripting::commit object.
#        Its second argument is a CCN::Scripting::run_once object.

sub commit($)
{
    my $self = shift;
    my $c    = shift; # A CCNCore::Scripting::commit object
    my $r    = shift; # A CCN::Scripting::run_once object
    $self->warn('No commit() method defined for '.ref($self).'.');
}

sub p() { return shift->{p}; }

sub sql_do_for              { my $self = shift; return $self->sql_do_for(@_); }
sub sql_get                 { my $self = shift; return $self->sql_get(@_); }
sub sql_get_array           { my $self = shift; return $self->sql_get_array(@_); }


# CHECKFOR $filename
sub CHECKFOR($)
{
    my $self = shift;
    my $file = shift;
    return if -e $file;
    $self->error ("Could not find $file, please check the Operating System installation.");
}

# PACKAGE $packagename
sub PACKAGE($)
{
    my $self = shift;
    my $package = shift;
    my $yum  = system "yum list installed '$package' >/dev/null 2>/dev/null";
    my $dpkg = system "dpkg -L '$package' > /dev/null 2>/dev/null";
    return if( $yum == 0 || $dpkg == 0 );
    $self->error ("Package $package is not installed. Please install it before proceeding.");
}

# ----- Disable/enable services ------

sub disable($)
{
    my $self = shift;
    my $service = shift;

    my $command;
    
    if( $self->p->operating_system =~ /debian|ccn/i )
    {
        $command = qq(/etc/init.d/'$service' stop; nice -n 19 sysv-rc-conf '$service' off);
    }
    if( $self->p->operating_system =~ /redhat|centos/i )
    {
        $command = qq(chkconfig disable '$service');
    }
    CCNCore::Scripting::DEFER("nice -n 19 $command");
}

sub enable($)
{
    my $self = shift;
    my $service = shift;

    my $command;
    
    if( $self->p->operating_system =~ /debian|ccn/i )
    {
        $command = qq(sysv-rc-conf --list '$service' | grep ':on' >/dev/null || sysv-rc-conf '$service' on);
    }
    if( $self->p->operating_system =~ /redhat|centos/i )
    {
        $command = qq(chkconfig enable '$service');
    }
    # Do not defer "enable" commands -- on Debian they restart (e.g.) MySQL,
    # which creates issues when trying to populate the database, etc.
    CCN::Scripting::exec_command($command);
}


# ------------- Name to IP mappings -----------------

# primary_ip: return the primary IP for a server.
our %primary_ip;

sub primary_ip($)
{
    my $self = shift;
    my $server_pk = shift;
    $self->die( "INTERNAL: primary_ip: no server" ) if not defined $server_pk;

    return $primary_ip{$server_pk} if exists $primary_ip{$server_pk};

    # ipnet_ip may be '' instead of NULL (because of the way we INSERT it)
    my $ip = $self->sql_get (<<'SQL', $server_pk);
        SELECT ipnet_ip
        FROM server_interface 
        WHERE server = ? AND ipnet_ip IS NOT NULL AND ipnet_ip <> ''
        ORDER BY rank ASC
SQL

    $self->die( "primary_ip: no ip for server $server_pk" ) if not defined $ip;

    $primary_ip{$server_pk} = $ip;
    return $ip;
}

# server_intf_ip: return the IP for a (server,interface) pair
#                 if the interface is undefined, return the primary IP for the server.
sub server_intf_ip($$) # server, intf
{
    my $self = shift;
    my ($server_pk,$intf) = @_;

    my $ip = $self->sql_get (<<'SQL', $server_pk, $intf);
        SELECT ipnet_ip
        FROM server_interface 
        WHERE server = ? AND ifname = ?;
SQL

    return defined $ip ? $ip : $self->primary_ip($server_pk);
}

# SQLite does not support IS TRUE
sub get_replication_intf_of($)
{
    my $self = shift;
    return $self->sql_get(<<'SQL',shift);
        SELECT ifname 
        FROM server_interface 
        WHERE server = ? AND do_replication = 'TRUE'
        ORDER BY mac ASC
SQL
}

sub get_realtime_intf_of($)
{
    my $self = shift;
    return $self->sql_get(<<'SQL',shift);
        SELECT ifname 
        FROM server_interface 
        WHERE server = ? AND do_realtime = 'TRUE' 
        ORDER BY mac ASC
SQL
}

sub get_replication_ip_of($)
{
    my $self = shift;
    my $server_pk = shift;
    return $self->server_intf_ip($server_pk,$self->get_replication_intf_of($server_pk));
}

sub get_realtime_ip_of($)
{
    my $self = shift;
    my $server_pk = shift;
    return $self->server_intf_ip($server_pk,$self->get_realtime_intf_of($server_pk));
}

sub make_password
{
    my $self = shift;
    return md5_hex(join('/',@_));
}

sub system_salt
{
    my $self = shift;
    $self->{system_salt} = $self->sql_get( q(SELECT salt FROM systems WHERE id = ?), $self->p->system_pk )
        if not exists $self->{system_salt};

    $self->die ('You must define a system-wide salt.')
        if not defined $self->{system_salt};

    return $self->{system_salt};
}

1;